﻿using System;
using System.Data;

namespace Fadd.Data
{
    /// <summary>
    /// Create a a transaction 
    /// </summary>
	public class Transaction : DataLayerImp, ITransaction
	{
	    private readonly IDbConnection _connection;
	    private IDbTransaction _transaction;
        private bool _isHandled;

	    /// <summary>
	    /// Initializes a new instance of the <see cref="DataLayer"/> class.
	    /// </summary>
		public Transaction(DataLayerImp dataLayerImp, IsolationLevel level)
			: base(dataLayerImp)
	    {
			Check.Require(dataLayerImp, "dataLayerImp");

			_connection = dataLayerImp.ConnectionHelper.CreateConnection();
	        _transaction = _connection.BeginTransaction(level);
	    	Logger = dataLayerImp.Logger;
	    }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataLayer"/> class.
        /// </summary>
        public Transaction(DataLayerImp dataLayerImp)
			: base(dataLayerImp)
        {
			_connection = dataLayerImp.ConnectionHelper.CreateConnection();
            _transaction = _connection.BeginTransaction();
			Logger = dataLayerImp.Logger;
		}

        internal Transaction(Transaction transaction) : base(transaction)
        {
            _connection = transaction._connection;
            _transaction = transaction._transaction;
        	Logger = transaction.Logger;
        }

        /// <summary>
        /// Cancels all changes.
        /// </summary>
	    public virtual void Cancel()
	    {
			Logger.Debug("Rolling back");
			TransactionEventArgs args = new TransactionEventArgs(this, _connection);
            Canceling(this, args);
			if (_transaction.Connection == null)
			{
				Logger.Error("No connection have been assigned, can't cancel transaction.");
				return;
			}

            _transaction.Rollback();
            _isHandled = true;
            Canceled(this, args);
			Logger.Trace("Done.");
        }

        /// <summary>
        /// Makes all changes permanent in the database.
        /// </summary>
	    public virtual void Commit()
	    {
			Logger.Debug("Committing.");
            TransactionEventArgs args = new TransactionEventArgs(this, _connection);
            Committing(this, args);
	        _transaction.Commit();
            _isHandled = true;
            Committed(this, args);
			Logger.Trace("Committed.");
		}

    	/// <summary>
    	/// Determines if we are a transaction.
    	/// </summary>
    	public override bool IsTransaction
    	{
    		get { return true; }
    	}

    	/// <summary>
	    /// We are not creating a connection, but returning the one used
	    /// in the transaction.
	    /// </summary>
	    /// <returns>A database connection.</returns>
	    protected override IDbConnection CreateConnection()
	    {
	        return _connection;
	    }

        /// <summary>
        /// Creates the command.
        /// </summary>
        /// <returns></returns>
        protected override IDbCommand CreateCommand(IDbConnection connection)
        {
            IDbCommand command = connection.CreateCommand();
            command.Transaction = _transaction;
            return command;
        }

        /// <summary>
	    /// Creates a new transaction.
	    /// </summary>
	    /// <param name="level">On of the <see cref="IsolationLevel"/>s.</param>
	    /// <returns>An object representing the new transaction.</returns>
	    /// <remarks>
	    /// Once the transaction has completed, you must explicitly commit or roll back the transaction by using the Commit or Rollback methods.
	    /// </remarks>
	    public ITransaction CreateTransaction(IsolationLevel level)
	    {
			Logger.Debug("Creating transaction wrapper, since a transaction have already been created.");
	        return new TransactionWrapper(this);
	    }

        

	    /// <summary>
	    /// Creates a new transaction.
	    /// </summary>
	    /// <returns>An object representing the new transaction.</returns>
	    /// <remarks>
	    /// Once the transaction has completed, you must explicitly commit or roll back the transaction by using the Commit or Rollback methods.
	    /// </remarks>
	    public ITransaction CreateTransaction()
	    {
			Logger.Debug("Creating transaction wrapper, since a transaction have already been created.");
			return new TransactionWrapper(this);
	    }

 
    	/// <summary>
        ///                     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public virtual void Dispose()
        {
            if (_transaction == null) return;
            if (_isHandled) return;

            _isHandled = true;

            Cancel();
            _transaction.Dispose();
            _transaction = null;

        	_connection.Close();
        	_connection.Dispose();
        }

        /// <summary>
        /// Transaction is about to be committed.
        /// </summary>
        public event EventHandler<TransactionEventArgs> Committing = delegate{};

        /// <summary>
        /// Transaction have been committed.
        /// </summary>
        public event EventHandler<TransactionEventArgs> Committed = delegate { };

        /// <summary>
        /// Transaction is about to be canceled.
        /// </summary>
        public event EventHandler<TransactionEventArgs> Canceling = delegate { };

        /// <summary>
        /// Transaction have been canceled.
        /// </summary>
        public event EventHandler<TransactionEventArgs> Canceled = delegate { };
	}

    /// <summary>
    /// Event arguments for transaction events.
    /// </summary>
    public class TransactionEventArgs : EventArgs
    {
        private readonly ITransaction _transaction;
        private readonly IDbConnection _connection;

        /// <summary>
        /// Initializes a new instance of the <see cref="TransactionEventArgs"/> class.
        /// </summary>
        /// <param name="transaction">Transaction.</param>
        /// <param name="connection">The connection.</param>
        public TransactionEventArgs(ITransaction transaction, IDbConnection connection)
        {
            Check.Require(transaction, "transaction");
            Check.Require(connection, "connection");

            _transaction = transaction;
            _connection = connection;
        }

        /// <summary>
        /// Transaction that the event is for.
        /// </summary>
        public ITransaction Transaction
        {
            get { return _transaction; }
        }

        /// <summary>
        /// Connection that the transaction is being invoked in.
        /// </summary>
        public IDbConnection Connection
        {
            get { return _connection; }
        }

		
    }
}
